package com.wondersgroup.core.frame;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * 通用DTO类的父类, 提供一些属性拷贝的方法
 * @author wanglei
 *
 */
public class CommonDomainDTO {

	/**
	 * 根据传入的白名单和黑名单列表检查该属性是否符合拷贝条件<br>
	 * # 符合拷贝条件返回true, 否则返回false
	 * @param propertyName
	 * @param whiteList 白名单列表
	 * @param blackList 黑名单列表
	 * @return
	 */
	private static boolean checkProperty(String propertyName, Set<String> whiteList, Set<String> blackList){
		if(whiteList!=null && !whiteList.contains(propertyName)){
			return false;
		}
		if(blackList!=null && blackList.contains(propertyName)){
			return false;
		}
		return true;
	}
	
	/**
	 * 拷贝属性到一个Map对象
	 * @param dest
	 * @param propertyName
	 * @param value
	 */
	private static void copyProperty(Map<String, Object> dest, String propertyName, Object value){
		dest.put(propertyName, value);
	}
	/**
	 * 拷贝属性到一个普通Bean
	 * @param dest
	 * @param destPropertyDescriptor
	 * @param value
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 */
	private static void copyProperty(Object dest, PropertyDescriptor destPropertyDescriptor, Object value) 
			throws IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		Method writeMethod = destPropertyDescriptor.getWriteMethod();
		if(writeMethod!=null){
			if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
				writeMethod.setAccessible(true);
			}
			writeMethod.invoke(dest, value);
		}
	}

	/**
	 * 将相同的属性从一个对象拷贝到另一个对象<br>
	 * # 支持Bean到Bean, Bean到Map, Map到Bean, Map到Map的属性拷贝<br>
	 * # 浅拷贝, 不支持类型转换, 类型不对会抛出异常<br>
	 * # 当一个属性名同时在白名单和黑名单中时以黑名单为准
	 * @param dest 目标对象
	 * @param orig 源对象
	 * @param whiteList 属性名白名单
	 * @param blackList 属性名黑名单
	 * @param ignoreNullValue 是否忽略null值
	 * @throws IntrospectionException
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 * @throws InvocationTargetException
	 */
	@SuppressWarnings("unchecked")
	public static void copyProperties(Object dest, Object orig, Set<String> whiteList, Set<String> blackList, boolean ignoreNullValue) 
			throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		if(dest==null || orig==null){
			return;
		}
		
		boolean copyToMap = false;
		Map<String, Object> mapDest = null;
		Map<String, PropertyDescriptor> destPropertyDescriptorMap = new HashMap<String, PropertyDescriptor>();
		//判断目标对象类型
		if(dest instanceof Map){
			copyToMap = true;
			mapDest = (Map<String, Object>) dest;
		}else{
			//获得所有的属性列表
			BeanInfo destBeanInfo = Introspector.getBeanInfo(dest.getClass());
			PropertyDescriptor[] destPropertyDescriptors = destBeanInfo.getPropertyDescriptors();
			for(int i=0; i<destPropertyDescriptors.length; i++){
				PropertyDescriptor propertyDescriptor = destPropertyDescriptors[i];
				destPropertyDescriptorMap.put(propertyDescriptor.getName(), propertyDescriptor);
			}
		}
		
		if(orig instanceof Map){
			//来源对象是Map的情况
			Iterator<Entry<String, Object>> entries = ((Map<String, Object>) orig).entrySet().iterator();
            while (entries.hasNext()) {
                Entry<String, Object> entry = entries.next();
                String name = entry.getKey();
                //判断该属性是否忽略
                if(!checkProperty(name, whiteList, blackList)){
                	continue;
                }
                
                Object value = entry.getValue();
                if(value==null && ignoreNullValue==true){
                	continue;
                }
                //根据目标对象类型调用不同的拷贝方法
                if(copyToMap){
                	copyProperty(mapDest, name, value);
                }else{
                	if(destPropertyDescriptorMap.containsKey(name)){
                		copyProperty(dest, destPropertyDescriptorMap.get(name), value);
                	}
                }
            }
		}else{
			BeanInfo origBeanInfo = Introspector.getBeanInfo(orig.getClass());
			PropertyDescriptor[] origPropertyDescriptors = origBeanInfo.getPropertyDescriptors();
			if(origPropertyDescriptors!=null && origPropertyDescriptors.length>0){
				for(int i=0; i<origPropertyDescriptors.length; i++){
					PropertyDescriptor propertyDescriptor = origPropertyDescriptors[i];
					String name = propertyDescriptor.getName();
					//判断该属性是否忽略
					if(!checkProperty(name, whiteList, blackList)){
	                	continue;
	                }
					
					Method readMethod = propertyDescriptor.getReadMethod();
					if(readMethod!=null){
						if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
							readMethod.setAccessible(true);
						}
						
						Object value = readMethod.invoke(orig);
						if(value==null && ignoreNullValue==true){
		                	continue;
		                }
						//根据目标对象类型调用不同的拷贝方法
		                if(copyToMap){
		                	copyProperty(mapDest, name, value);
		                }else{
		                	if(destPropertyDescriptorMap.containsKey(name)){
		                		copyProperty(dest, destPropertyDescriptorMap.get(name), value);
		                	}
		                }
					}
				}
			}
		}
	}
	
	//================================================================================================
	/**
	 * 拷贝DTO对象中的值到domain实例中<br>
	 * # 支持Bean到Bean, Bean到Map, Map到Bean, Map到Map的属性拷贝
	 * @param targetInstance
	 * @param clazz domain的类型
	 * @param sourceInstance
	 * @return
	 * @throws Exception
	 */
	public static <T> T toEntity(T targetInstance, Class<T> clazz,
			Object sourceInstance) throws Exception {
		return toEntity(targetInstance, clazz, sourceInstance, null, null, false);
	}
	
	/**
	 * 拷贝DTO对象中的值到domain实例中<br>
	 * # 支持Bean到Bean, Bean到Map, Map到Bean, Map到Map的属性拷贝<br>
	 * # 当一个属性名同时在白名单和黑名单中时以黑名单为准
	 * @param targetInstance
	 * @param clazz
	 * @param sourceInstance
	 * @param whiteList 属性名白名单
	 * @param blackList 属性名黑名单
	 * @param ignoreNullValue
	 * @return
	 * @throws Exception
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static <T> T toEntity(T targetInstance, Class<T> clazz,
			Object sourceInstance, Set<String> whiteList, Set<String> blackList, boolean ignoreNullValue) throws Exception {
		if (targetInstance == null) {
			targetInstance = clazz.newInstance();
		}

		Object id = null;
		PropertyDescriptor pd = null;


		copyProperties(targetInstance, sourceInstance, whiteList, blackList, ignoreNullValue);


		return targetInstance;
	}
	
	/**
	 * 获取dtoInstance中某个属性的值
	 * @param dtoInstance
	 * @param propertyName 属性名称
	 * @param returnClazz 需要的返回类型
	 * @return
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <T> T getPropertyValue(Object dtoInstance, String propertyName, Class<T> returnClazz){
		T returnValue = null;
		if (dtoInstance instanceof Map) {
			returnValue = (T) ((Map) dtoInstance).get(propertyName);
		} else {
			try {
				PropertyDescriptor pd = new PropertyDescriptor(propertyName, dtoInstance.getClass());
				Method getMethod = pd.getReadMethod();
				returnValue = (T) getMethod.invoke(dtoInstance);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return returnValue;
	}
}
